package com.cdck.safe.dete.controller;

import cn.hutool.core.date.LocalDateTimeUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.cdck.safe.dete.common.result.Result;
import com.cdck.safe.dete.common.util.SecurityUtils;
import com.cdck.safe.dete.model.entity.CheckProject;
import com.cdck.safe.dete.model.entity.CheckRecord;
import com.cdck.safe.dete.service.CheckProjectService;
import com.cdck.safe.dete.service.CheckRecordService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @description 客户端控制
 * @author xwj
 * @Date 2024/2/29 下午 5:14
 */
@RestController
@RequestMapping("project")
@RequiredArgsConstructor
public class ClientQueueController {

    /*
      1 分配用户体检套餐
      2 计算出最优的剩下的未检查项目的检查顺序
      3 对指定项目进行排队
      4 取消对指定项目的排队
      5 查询指定项目的排队情况
      6 查询自身的所有检查项目(标记已检查项目及未检查项目)
     */

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Autowired
    private CheckRecordService recordService;

    @Autowired
    private CheckProjectService projectService;

    /**
     * 已检查
     */
    private static final String IS_CHECK = "1";
    /**
     * 未检查
     */
    private static final String NOT_CHECK = "0";
    /**
     * 在排队的项目key
     */
    private static final String CHECKING = "queue:checking";
    /**
     * 用户待检查的项目key
     */
    private static final String USER_CHECK = "queue:unChecking:";
    /**
     * 体检项目状态
     */
    private static final Integer FINISHED = 1;

    /**
     * 分配用户套餐,并存放在redis中
     */
    @GetMapping("/assign")
    public Result<List<CheckProject>> assignProjectToUser() {
        Long userId = SecurityUtils.getUserId();
        if (Objects.isNull(userId)) return Result.failed("用户信息错误");
        List<CheckProject> checkProjects = projectService.queryAllProject();
//        Set<Long> projectIds = checkProjects.stream().map(CheckProject::getId).collect(Collectors.toSet());
//        projectIds.forEach(id -> saveProjectIdByUserId(userId, String.valueOf(id), NOT_CHECK));
        checkProjects.forEach(project -> {
            saveProjectIdByUserId(userId, String.valueOf(project.getId()), project.getName()+":"+project.getDuration());
        });
        return Result.success(checkProjects);
    }

    /**
     * 查询当前用户待检查的项目
     */
    @GetMapping("/queryUnCheckProject")
    public Result<Boolean> queryUnCheckProject() {
        Long userId = SecurityUtils.getUserId();
        if (Objects.isNull(userId)) return Result.failed("用户信息错误");
        Map<Object, Object> allProjectIdsAndDataByUserId = getAllProjectIdsAndDataByUserId(userId);
        int size = allProjectIdsAndDataByUserId.size();
        if (size > 0) {
            return Result.success(true);
        }
        return Result.success(false);
    }

    /**
     *  计算最优排序
     */
    @GetMapping("/calculate")
    public Result<List<CheckProject>> CalculateOptimalSort() {
        //1 先获取当前用户待做所有的项目
        Long userId = SecurityUtils.getUserId();
        if (Objects.isNull(userId)) return Result.failed("用户信息错误");
        Map<Object, Object> remainingProjects = getAllProjectIdsAndDataByUserId(userId);

        //2 获取当前正在排队的项目的有序列表
        List<String> sortCheckingProject = getSortCheckingProject();

        //3 遍历当前用户待做所有的项目，存储其Id值
        List<String> unCheckProject = new ArrayList<>();
        for (Map.Entry<Object, Object> entry : remainingProjects.entrySet()) {
            if (entry.getKey() instanceof String && entry.getValue() instanceof String) {
                Long projectId = Long.valueOf((String) entry.getKey());
//                String projectName = ((String) entry.getValue()).split(":")[0];
//                String projectTime = ((String) entry.getValue()).split(":")[1];
//                System.out.println(projectId+","+projectTime+","+projectName);
                unCheckProject.add(String.valueOf(projectId));
            } else {
                // 处理非String类型的情况，或者抛出异常
                throw new IllegalArgumentException("不是string类型");
            }
        }

        //4 对待体检项目进行排序
        List<String> unCheckProjectSortedIds = sortProject(unCheckProject, sortCheckingProject);

        //5 处理优先级

        //6 根据最终排序完成的项目,查询其完整信息
        String sortedIdsInSqlFormat = unCheckProjectSortedIds.stream()
                .map(id -> "'" + id + "'")
                .collect(Collectors.joining(", "));
        LambdaQueryWrapper<CheckProject> wrapper = new LambdaQueryWrapper<>();
        //拼接sql让数据的顺序根据排序好的项目顺序
        wrapper.in(CheckProject::getId, unCheckProjectSortedIds)
                .last("ORDER BY FIELD(id, " + sortedIdsInSqlFormat + ")");
        List<CheckProject> checkProjects = projectService.list(wrapper);

        //7 返回结果
        return Result.success(checkProjects);
    }

    /**
     * 获取所有正在体检的项目
     * @return
     */
    public List<String> getSortCheckingProject() {
        List<String> projectList = new ArrayList<>();
        Set<Object> allMembersFromZSet = getAllMembersFromZSet(CHECKING);
        // 将Object集合转换为String集合
        for (Object member : allMembersFromZSet) {
            if (member instanceof String) {
                projectList.add((String) member);
            }
        }
        return projectList;
    }

    /**
     * 排序待体检项目
     * 若有待体检项目不存在与有序集合中，添加
     * @param unCheckProject
     * @param sortCheckingProject
     * @return
     */
    public List<String> sortProject(List<String> unCheckProject, List<String> sortCheckingProject){
         return unCheckProject.stream()
                .sorted((projectId1, projectId2) -> {
                    // 如果projectId1和projectId2都不在sortCheckingProject中，则保持原顺序
                    if (!sortCheckingProject.contains(projectId1) && !sortCheckingProject.contains(projectId2)) {
                        return 0;
                    }
                    // 如果projectId1或projectId2至少有一个不在sortCheckingProject中，则该项目应排在前面
                    if (!sortCheckingProject.contains(projectId1) || !sortCheckingProject.contains(projectId2)) {
                        return !sortCheckingProject.contains(projectId1) ? -1 : 1;
                    }
                    // 如果都在sortCheckingProject中，则按其顺序排列
                    int index1 = sortCheckingProject.indexOf(projectId1);
                    int index2 = sortCheckingProject.indexOf(projectId2);
                    return Integer.compare(index1, index2);
                })
                .collect(Collectors.toList());
    }

    /**
     * 排上
     * @param projectId
     * @return
     */
    @Transactional
    @GetMapping("/lineUp/{projectId}")
    public Result<String> lineUp(@PathVariable String projectId) {
        Long userId = SecurityUtils.getUserId();
        if (Objects.isNull(userId)) return Result.failed("用户信息错误");
        //查询项目信息
        CheckProject byId = projectService.getById(projectId);

        Boolean isChecking = recordService.isChecking(userId, projectId);

        if (isChecking) return Result.failed("当前正在排队，分身乏术!");

        try {
            //从待检查项目中去除
            deleteProjectIdByUserId(userId, projectId);
            //查询正在排队的人数
            Double score = score(CHECKING, projectId);
            if (Objects.nonNull(score)){
                //正在排队人数+1
                Double aDouble = UScore(CHECKING, projectId, byId.getDuration());
            }else {
                //没有人排队，或者该项目未排列，加入对列
                addProjectToZSet(CHECKING, projectId, byId.getDuration());
            }
            //加入记录
            saveRecord(projectId, userId);
            return Result.success();
        }catch (Exception e){
            redisTemplate.opsForHash().delete(String.valueOf(userId));
            assignProjectToUser();
            return Result.failed("重头开始体检吧,衰仔!");
        }
    }

    /**
     * 增加体检记录
     * @param projectId
     * @param userId
     */
    public void saveRecord(String projectId, Long userId){
        CheckRecord checkRecord = new CheckRecord();
        checkRecord.setStartTime(LocalDateTime.now());
        checkRecord.setUserId(userId);
        checkRecord.setProjectId(Long.valueOf(projectId));
        recordService.saveCheckRecord(checkRecord);
    }

    /**
     * 不排了
     * @param projectId
     * @return
     */
    @Transactional
    @GetMapping("/lineOut/{projectId}")
    public Result<String> lineOut(@PathVariable String projectId) {
        Long userId = SecurityUtils.getUserId();
        if (Objects.isNull(userId)) return Result.failed("用户信息错误");
        //查询项目信息
        CheckProject byId = projectService.getById(projectId);
        try {
            //查询正在排队的人数
            Double score = score(CHECKING, projectId);
            if (Objects.nonNull(score)){
                if (score < byId.getDuration()){
                    removeProjectFromZSet(CHECKING, projectId);
                }else {
                    DScore(CHECKING, projectId, byId.getDuration());
                }
                saveProjectIdByUserId(userId, projectId, byId.getName()+ ":"+ byId.getDuration());
            }else {
                return Result.failed("你也没排队啊!");
            }
            cancelRecord(projectId, userId);
            return Result.success();
        }catch (Exception e){
            return Result.failed("");
        }
    }

    /**
     * 未做，记录取消
     * @param projectId
     * @param userId
     */
    public void cancelRecord(String projectId, Long userId){
        LambdaQueryWrapper<CheckRecord> wrapper = new LambdaQueryWrapper<>();
        LocalDateTime now = LocalDateTime.now();
        Date todayZero = Date.from(now.truncatedTo(ChronoUnit.DAYS).atZone(ZoneId.systemDefault()).toInstant());
        wrapper.eq(CheckRecord::getProjectId, projectId)
                .eq(CheckRecord::getUserId, userId)
                .isNull(CheckRecord::getEndTime)
                .ge(CheckRecord::getStartTime, todayZero);
        CheckRecord lining = recordService.getOne(wrapper);
        LocalDateTime startTime = lining.getStartTime();
        long minutes = Duration.between(startTime, now).toMinutes();
        lining.setEndTime(now);
        lining.setDuration((double) minutes);
        recordService.updateCheckRecord(lining.getId(), lining);
    }

    /**
     * 做完啦
     * @param projectId
     * @return
     */
    @GetMapping("/finishedDo/{projectId}")
    public Result<String> finishedDo(@PathVariable String projectId) {
        Long userId = SecurityUtils.getUserId();
        if (Objects.isNull(userId)) return Result.failed("用户信息错误");
        //查询项目信息
        CheckProject byId = projectService.getById(projectId);
        try {
            //查询正在排队的人数
            Double score = score(CHECKING, projectId);
            if (Objects.nonNull(score)){
                if (score < byId.getDuration()){
                    removeProjectFromZSet(CHECKING, projectId);
                }else {
                    Double aDouble = DScore(CHECKING, projectId, byId.getDuration());
                }
            }else {
                return Result.failed("你也没做啊!");
            }
            finishRecord(projectId, userId);
            return Result.success();
        }catch (Exception e){
            return Result.failed("");
        }
    }

    /**
     * 昨晚
     * @param projectId
     * @param userId
     */
    public void finishRecord(String projectId, Long userId){
        LambdaQueryWrapper<CheckRecord> wrapper = new LambdaQueryWrapper<>();
        LocalDateTime now = LocalDateTime.now();
        Date todayZero = Date.from(now.truncatedTo(ChronoUnit.DAYS).atZone(ZoneId.systemDefault()).toInstant());
        wrapper.eq(CheckRecord::getProjectId, projectId)
                .eq(CheckRecord::getUserId, userId)
                .isNull(CheckRecord::getEndTime)
                .ge(CheckRecord::getStartTime, todayZero);
        CheckRecord lining = recordService.getOne(wrapper);
        LocalDateTime startTime = lining.getStartTime();
        long minutes = Duration.between(startTime, now).toMinutes();
        lining.setEndTime(now);
        lining.setDuration((double) minutes);
        lining.setFinished(FINISHED);
        recordService.updateCheckRecord(lining.getId(), lining);
    }

    /**
     * 查询指定项目的排队情况
     * @param projectId
     * @return
     */
    @GetMapping("/lineConditionByProjectId/{projectId}")
    public Result<Map<String, String>> lineConditionByProjectId(@PathVariable String projectId) {
        Double score = score(CHECKING, projectId);
        Map<String, String> map = new HashMap<>();
        if (Objects.nonNull(score) && score != 0){
            CheckProject byId = projectService.getById(projectId);
            Double duration = byId.getDuration();
            map.put("time", String.valueOf(score));
            map.put("members", String.valueOf(score/duration));
        }else {
            map.put("time", "0");
            map.put("members", "0");
        }
        return Result.success(map);
    }

    //-------------------- redis HASH -------------------


    /**
     * HASH
     * 存储 userId 对应的 projectId 数据
     * @param userId
     * @param projectId
     * @param data
     */
    public void saveProjectIdByUserId(Long userId, String projectId, String data) {
        redisTemplate.opsForHash().put(USER_CHECK + userId, projectId, data);
    }

    /**
     * HASH
     * 根据 userId 获取所有 projectId 及其数据
     * @param userId
     * @return
     */
    public Map<Object, Object> getAllProjectIdsAndDataByUserId(Long userId) {
        return redisTemplate.opsForHash().entries(USER_CHECK + userId);
    }

    /**
     * HASH
     *  根据 userId 获取对应的 projectId 数据
     * @param userId
     * @param projectId
     * @return
     */
    public String getProjectIdByUserId(Long userId, String projectId) {
        return (String) redisTemplate.opsForHash().get(String.valueOf(userId), projectId);
    }

    /**
     * HASH
     * 修改 userId 对应的 projectId 数据
     * @param userId
     * @param projectId
     * @param newData
     */
    public void updateProjectIdByUserId(Long userId, String projectId, String newData) {
        redisTemplate.opsForHash().put(String.valueOf(userId), projectId, newData);
    }

    /**
     * HASH
     * 删除指定 userId 下的 projectId 数据
     * @param userId
     * @param projectId
     */
    public void deleteProjectIdByUserId(Long userId, String projectId) {
        redisTemplate.opsForHash().delete(USER_CHECK + userId , projectId);
    }


    //-------------------- redis ZSet -------------------



    /**
     * ZSet
     * 添加成员到有序集合，同时设置分数
     * @param key 键
     * @param projectId 项目id
     * @param score 分数
     */
    public void addProjectToZSet(String key, String projectId, double score) {
        redisTemplate.opsForZSet().add(key, projectId, score);
    }

    /**
     * ZSet
     * 批量添加成员到有序集合
     * @param key 键
     * @param tuples 项目集合
     */
    public void addProjectToZSet(String key, Set<ZSetOperations.TypedTuple<Object>> tuples) {
        redisTemplate.opsForZSet().add(key, tuples);
    }

    /**
     * ZSet
     * 从有序集合中获取所有成员
     * @param key 键
     * @return 有序集合
     */
    public Set<Object> getAllMembersFromZSet(String key) {
        return redisTemplate.opsForZSet().range(key, 0, -1);
    }

    /**
     * ZSet
     * 获取成员的分数
     * @param key 键
     * @param projectId 项目id
     * @return 分数
     */
    public Double score(String key, String projectId) {
        return redisTemplate.opsForZSet().score(key, projectId);
    }

    /**
     * ZSet
     * 获取指定顺序的成员集合
     * @param key 键
     * @param start 开始位置
     * @param end 最终位置
     * @return 有序集合
     */
    public Set<ZSetOperations.TypedTuple<Object>> rangeWithScores(String key, long start, long end) {
        return redisTemplate.opsForZSet().rangeWithScores(key, start, end);
    }

    /**
     * ZSet
     * 减少项目分数
     * @param key 键
     * @param projectId 项目id
     * @param newScore 减少的分数
     * @return 最终分数
     */
    public Double DScore(String key, String projectId, double newScore) {
        return redisTemplate.opsForZSet().incrementScore(key, projectId, 0 - newScore);
    }

    /**
     * ZSet
     * 增加成员的分数
     * @param key 键
     * @param projectId 项目id
     * @param newScore 分数
     * @return 最终分数
     */
    public Double UScore(String key, String projectId, double newScore) {
        return redisTemplate.opsForZSet().incrementScore(key, projectId, newScore);
    }

    /**
     * ZSet
     * 删除有序集合中的成员
     * @param key 键
     * @param projectId 项目id
     * @return 移除数量
     */
    public Long removeProjectFromZSet(String key, String projectId) {
        return redisTemplate.opsForZSet().remove(key, projectId);
    }

    /**
     * ZSet
     * 根据分数范围获取有序集合中的成员
     * @param key 键
     * @param min 最小分数
     * @param max 最大分数
     * @return 有序集合
     */
    public Set<Object> getMembersByScoreRange(String key, double min, double max) {
        return redisTemplate.opsForZSet().rangeByScore(key, min, max);
    }
}
